﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using Pixysoft.Framework.Sockets.Core;
using System.Threading;
using System.Net;
using Pixysoft.Framework.Sockets.Codec;

namespace Pixysoft.Framework.Sockets.Controller
{
    class SessionPoolController : ISessionFactory
    {
        private bool isConnected = false;

        private TcpListener listener;//监听器 不能用socket 会导致ip链接出错

        private static ReaderWriterLock Lock_Dictionary = new ReaderWriterLock();

        //sessionid:session
        private Dictionary<string, ISocketIoSession> pools = new Dictionary<string, ISocketIoSession>();

        //usercode:sessionid
        private Dictionary<string, string> userMapping = new Dictionary<string, string>();


        private ConnectionTimeoutTrigger timeoutTrigger = new ConnectionTimeoutTrigger();

        private ISessionCodecFactory codec = new SocketSessionCodecFactory();


        private static volatile SessionPoolController instance;

        private static object syncRoot = new Object();

        public static SessionPoolController Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                        {
                            instance = new SessionPoolController();
                        }
                    }
                }
                return instance;
            }
        }

        public void SetCodecFactory(ISessionCodecFactory codec)
        {
            this.codec = codec;
        }

        public bool Start(int localport)
        {
            Stop();

            LoggerHelper.Info("begin start socket listener at " + localport);

            listener = new TcpListener(localport);

            try
            {
                //What that means is that when someone tries to connect, but your server hasn't Accept()ed the connection yet, those clients are in the "backlog" queue. You've set the size to 1, so only 1 client can be waiting to connect at a time.

                listener.Start();
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                Stop();

                return false;
            }

            LoggerHelper.Info("socket listener begin accept socket.");

            try
            {
                listener.BeginAcceptSocket(new AsyncCallback(OnAccept), null);
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                Stop();

                return false;
            }

            ThreadPoolController.Register(timeoutTrigger, this, SettingHelper.default_interval_trigger_connection, false);

            isConnected = true;

            LoggerHelper.Debug("socket listener started at {0}", localport);

            return isConnected;
        }

        public void Stop()
        {
            LoggerHelper.Debug("socket listener begin stop.");

            isConnected = false;

            if (listener != null)
            {
                try
                {
                    listener.Stop();
                }
                catch (Exception ex)
                {
                    LoggerHelper.Fatal(ex);
                }
            }

            listener = null;

            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                foreach (ISocketIoSession conn in pools.Values)
                {
                    try
                    {
                        conn.Close();

                        conn.Dispose();
                    }
                    catch (Exception ex)
                    {
                        LoggerHelper.Error(ex.ToString());
                    }
                }

                pools.Clear();
            }
            catch (Exception ex)
            {
                LoggerHelper.Error(ex.ToString());
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }

            ThreadPoolController.Unregister(timeoutTrigger);

            LoggerHelper.Debug("socket listener stopped.");
        }

        public int SessionPoolCount
        {
            get
            {
                Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

                try
                {
                    return pools.Count;
                }
                finally
                {
                    Lock_Dictionary.ReleaseReaderLock();
                }
            }
        }

        public bool IsConnected
        {
            get
            {
                return this.isConnected;
            }
        }

        public void RemoveTimeoutSession()
        {
            List<ISocketIoSession> removeList = new List<ISocketIoSession>();

            Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

            try
            {
                foreach (ISocketIoSession session in pools.Values)
                {
                    if (session.IsTimeout || session.IsLoginningTimeout)// 连接无响应超时+没有登录  才会下线
                    {
                        // remove timeout session.

                        LoggerHelper.Info(session.Identifier + ":connection is timeout, remove from pool.");

                        try
                        {
                            removeList.Add(session);
                        }
                        catch (Exception ex)
                        {
                            LoggerHelper.Fatal(ex);
                        }
                    }
                }
            }
            finally
            {
                Lock_Dictionary.ReleaseReaderLock();
            }


            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                foreach (ISocketIoSession session in removeList)
                {
                    this.UnsafeRemoveSession(session);
                }
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }

            foreach (ISocketIoSession session in removeList)
            {
                session.Close();

                session.Dispose();
            }
        }



        public bool ContainsSession(string identifier)
        {
            Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

            try
            {
                return pools.ContainsKey(identifier);
            }
            finally
            {
                Lock_Dictionary.ReleaseReaderLock();
            }
        }

        public ISocketIoSession GetSession(string identifier)
        {
            Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

            try
            {
                if (pools.ContainsKey(identifier))
                    return pools[identifier];

                return null;
            }
            finally
            {
                Lock_Dictionary.ReleaseReaderLock();
            }
        }

        public ISocketIoSession GetSessionByUserCode(string userCode)
        {
            Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

            try
            {
                if (string.IsNullOrEmpty(userCode))
                    return null;

                userCode = userCode.Trim().ToUpper();

                if (!userMapping.ContainsKey(userCode))
                    return null;

                string identifier = userMapping[userCode];

                if (string.IsNullOrEmpty(identifier))
                    return null;

                if (!pools.ContainsKey(identifier))
                    return null;

                LoggerHelper.Debug("get session by usercode:{0} id:{1}", userCode, identifier);

                return pools[identifier];
            }
            finally
            {
                Lock_Dictionary.ReleaseReaderLock();
            }
        }

        internal void AddSession(ISocketIoSession session)
        {
            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                pools.Add(session.Identifier, session);
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }
        }

        internal void LoginSession(string identifier, string usercode)
        {
            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                if (string.IsNullOrEmpty(usercode))
                    return;

                usercode = usercode.Trim().ToUpper();

                if (userMapping.ContainsKey(usercode))
                {
                    userMapping.Remove(usercode);
                }

                userMapping.Add(usercode, identifier);

                LoggerHelper.Debug("login session usercode:{0} id:{1}", usercode, identifier);
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }
        }

        internal void LogoutSession(string usercode)
        {
            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                if (string.IsNullOrEmpty(usercode))
                    return;

                usercode = usercode.Trim().ToUpper();

                if (userMapping.ContainsKey(usercode))
                {
                    userMapping.Remove(usercode);

                    LoggerHelper.Debug("logout session usercode:{0}", usercode);
                }
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }
        }

        public ISocketIoSession[] GetSessions()
        {
            Lock_Dictionary.AcquireReaderLock(Timeout.Infinite);

            try
            {
                List<ISocketIoSession> sessions = new List<ISocketIoSession>();

                foreach (ISocketIoSession session in pools.Values)
                {
                    sessions.Add(session);
                }

                return sessions.ToArray();
            }
            finally
            {
                Lock_Dictionary.ReleaseReaderLock();
            }
        }

        /// <summary>
        /// 从连接池移除 并close/dispose链接
        /// </summary>
        /// <param name="session"></param>
        public void CloseSession(ISocketIoSession session)
        {
            LoggerHelper.Debug("socket listener remove connection. identifier:" + session.Identifier);

            Lock_Dictionary.AcquireWriterLock(Timeout.Infinite);

            try
            {
                UnsafeRemoveSession(session);
            }
            finally
            {
                Lock_Dictionary.ReleaseWriterLock();
            }

            session.Close();

            session.Dispose();
        }


        private void UnsafeRemoveSession(ISocketIoSession session)
        {
            string identifier = session.Identifier;

            LoggerHelper.Info("socket listener begin remove connection, id:" + identifier);

            pools.Remove(identifier);

            string usercode = session.GetProfile().UserCode;

            if (!string.IsNullOrEmpty(usercode))
            {
                usercode = usercode.Trim().ToUpper();

                userMapping.Remove(usercode);
            }
        }

        private void OnAccept(IAsyncResult ar)
        {
            //链接处理

            Socket clientSocket = null;

            LoggerHelper.Debug("socket listener end accept socket.");

            try
            {
                clientSocket = listener.EndAcceptSocket(ar);
            }
            catch (Exception ex)
            {
                if (!isConnected)
                    return;

                LoggerHelper.Fatal(ex);

                LoggerHelper.Fatal("socket server is unstable, MAYBE STOP.");

                return;
            }

            LoggerHelper.Debug("socket listener begin accept socket again.");

            try
            {
                listener.BeginAcceptSocket(new AsyncCallback(OnAccept), null);
            }
            catch (Exception ex)
            {
                if (!isConnected)
                    return;

                LoggerHelper.Fatal(ex);

                LoggerHelper.Fatal("socket server is unstable, MAYBE STOP.");

                return;
            }

            SessionCreated(clientSocket);
        }

        private void SessionCreated(Socket clientSocket)
        {
            string identifier = SocketIoSession.CreateIdentifier(clientSocket);

            if (this.ContainsSession(identifier))
            {
                ISocketIoSession existSession = this.GetSession(identifier);

                this.CloseSession(existSession);
            }

            ISocketIoSession mySession = this.CreateSession(clientSocket);

            try
            {
                int socketcount = this.SessionPoolCount;

                if (socketcount >= SettingHelper.default_max_connnection_socket)
                {
                    LoggerHelper.Info("socket pool reach max capacity, close current socket, id:{0}", mySession.Identifier);

                    this.CloseSession(mySession);

                    return;
                }

                LoggerHelper.Debug("socket listener create new connection at {0}, id:{1}.", clientSocket.RemoteEndPoint, identifier);

                GlobalPerformanceAnalyzer.Instance.CreateSession();// ----------------------------------------------
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                this.CloseSession(mySession);
            }
        }

        private ISocketIoSession CreateSession(Socket clientSocket)
        {
            ISocketIoSession isession = ServerSocketIoSession.CreateSession(codec.CreateEncoder(), codec.CreateDecoder(), clientSocket);

            this.AddSession(isession);

            return isession;
        }
    }
}
